﻿using System;
using System.Web.Mvc;

namespace Lilium.Web.Mvc.Binding
{
	public class DateAndTimeModelBinder : IModelBinder
	{
		public DateAndTimeModelBinder() { }

		public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
		{
			if (bindingContext == null)
			{
				throw new ArgumentNullException("bindingContext");
			}


            DateTime? dateTimeAttempt;

            var valueResult = bindingContext.ValueProvider.GetValue(bindingContext.ModelName);
            if (valueResult != null)
            {
                dateTimeAttempt = ((Nullable<DateTime>)valueResult.ConvertTo(typeof(Nullable<DateTime>)));
                if (dateTimeAttempt.HasValue)
                    return dateTimeAttempt.Value;
            }

			//Maybe we're lucky and they just want a DateTime the regular way.
			dateTimeAttempt = GetA<DateTime>(bindingContext, controllerContext, "DateTime");
			if (dateTimeAttempt != null)
			{
				return dateTimeAttempt.Value;
			}

			//If they haven't set Month,Day,Year OR Date, set "date" and get ready for an attempt
			if (this.MonthDayYearSet == false && this.DateSet == false)
			{
				this.Date = "Date";
			}

			//If they haven't set Hour, Minute, Second OR Time, set "time" and get ready for an attempt
			if (this.HourMinuteSecondSet == false && this.TimeSet == false)
			{
				this.Time = "Time";
			}

			//Did they want the Date *and* Time?
			DateTime? dateAttempt = GetA<DateTime>(bindingContext, controllerContext, this.Date);
			DateTime? timeAttempt = GetA<DateTime>(bindingContext, controllerContext, this.Time);

			//Maybe they wanted the Time via parts
			if (this.HourMinuteSecondSet)
			{
				timeAttempt = new DateTime(
				DateTime.MinValue.Year, DateTime.MinValue.Month, DateTime.MinValue.Day,
				GetA<int>(bindingContext, controllerContext, this.Hour).Value,
				GetA<int>(bindingContext, controllerContext, this.Minute).Value,
				GetA<int>(bindingContext, controllerContext, this.Second).Value);
			}

			//Maybe they wanted the Date via parts
			if (this.MonthDayYearSet)
			{
				dateAttempt = new DateTime(
				GetA<int>(bindingContext, controllerContext, this.Year).Value,
				GetA<int>(bindingContext, controllerContext, this.Month).Value,
				GetA<int>(bindingContext, controllerContext, this.Day).Value,
				DateTime.MinValue.Hour, DateTime.MinValue.Minute, DateTime.MinValue.Second);
			}

			//If we got both parts, assemble them!
			if (dateAttempt != null && timeAttempt != null)
			{
				return new DateTime(dateAttempt.Value.Year,
				dateAttempt.Value.Month,
				dateAttempt.Value.Day,
				timeAttempt.Value.Hour,
				timeAttempt.Value.Minute,
				timeAttempt.Value.Second);
			}
			//Only got one half? Return as much as we have!
			return dateAttempt ?? timeAttempt;
		}

		private Nullable<T> GetA<T>(ModelBindingContext bindingContext, ControllerContext controllerContext, string key) where T : struct
		{
			if (bindingContext == null) throw new ArgumentNullException("bindingContext");

			if (string.IsNullOrEmpty(key)) return null;
			//Try it with the prefix...
			var valueResult = bindingContext.ValueProvider.GetValue(bindingContext.ModelName + "." + key);
			//Didn't work? Try without the prefix if needed...

			if (valueResult == null && bindingContext.FallbackToEmptyPrefix == true)
			{
				valueResult = bindingContext.ValueProvider.GetValue(key);
			}
			if (valueResult == null)
			{
				return null;
			}
			return (Nullable<T>)valueResult.ConvertTo(typeof(T));
		}

		public string Date { get; set; }
		public string Time { get; set; }

		public string Month { get; set; }
		public string Day { get; set; }
		public string Year { get; set; }
		public string Hour { get; set; }
		public string Minute { get; set; }
		public string Second { get; set; }

        public bool DateSet { get { return !string.IsNullOrEmpty(Date); } }
        public bool MonthDayYearSet { get { return !(string.IsNullOrEmpty(Month) && string.IsNullOrEmpty(Day) && string.IsNullOrEmpty(Year)); } }

        public bool TimeSet { get { return !string.IsNullOrEmpty(Time); } }
        public bool HourMinuteSecondSet { get { return !(string.IsNullOrEmpty(Hour) && string.IsNullOrEmpty(Minute) && string.IsNullOrEmpty(Second)); } }

	}
}